package com.yeyks.service.impl;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.google.common.collect.Maps;
import com.yeyks.account.service.SysAppMenuService;
import com.yeyks.common.appcommon.result.CommonResult;
import com.yeyks.common.exception.TokenInvalidException;
import com.yeyks.common.utils.apputil.redis.RedisService;
import com.yeyks.common.utils.apputil.rediskey.BuilderKey;
import com.yeyks.common.utils.apputil.rediskey.RedisKey;
import com.yeyks.common.utils.apputil.type.AccountType;
import com.yeyks.dao.AppAccountInfoMapper;
import com.yeyks.entity.AppAccountInfo;
import com.yeyks.service.AppAccountTokenService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author Administrator
 */
@Service
@Slf4j
public class AppAccountTokenServiceImpl implements AppAccountTokenService {


    @Autowired
    private RedisService redisService;

    @Autowired
    private AppAccountInfoMapper accountInfoMapper;

    @Autowired
    private SysAppMenuService appMenuService;

    private ReadWriteLock lock = new ReentrantReadWriteLock();

    /**
     * token秘钥
     */
    private static final String SECRET = "269e2643-b259-419c-bc4d-23b34d09f9f4";
    /**
     * token 当前时间
     */
    public static final int CALENDAR_FIELD = Calendar.DATE;

    /**
     * 时间天数, 这个天数一定要比redis 的天数大
     */
    public static final int CALENDAR_INTERVAL = 30;

    @Override
    public String userIdToken(Integer accountId) {
        return this.createToken(accountId.toString());
    }

    private String createToken(String accountId) {
        Date iatDate = new Date();
        // expire time
        Calendar nowTime = Calendar.getInstance();
        nowTime.add(CALENDAR_FIELD, CALENDAR_INTERVAL);
        Date expiresDate = nowTime.getTime();

        // header Map
        Map<String, Object> map = Maps.newHashMap();
        map.put("alg", "HMAC256");
        map.put("type", "JWT");

        // build token
        // param backups {iss:Service, aud:APP}
        String token = JWT.create()
                // header
                .withHeader(map)
                .withClaim("user_id", accountId)
                // sign time
                .withIssuedAt(iatDate)
                // expire time
                .withExpiresAt(expiresDate)
                // signature
                .sign(Algorithm.HMAC256(SECRET));
        return token;
    }

    /**
     * 解密Token，获得内容
     *
     * @param token
     * @return
     * @throws Exception
     */
    public static Map<String, Claim> getClaims(String token) {
        JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
        DecodedJWT jwt = verifier.verify(token);
        return (jwt == null || jwt.getClaims() == null) ? null : jwt.getClaims();
    }

    /**
     * 根据Token获取userId
     *
     * @param token
     * @return user_id
     */
    @Override
    public String getUserId(String token) throws TokenInvalidException {

        String userId = null;
        try {
            Map<String, Claim> claims = getClaims(token);
            Claim userIdClaim = claims.get("user_id");
            userId = userIdClaim == null ? null : userIdClaim.asString();
        } catch (Throwable e) {
            e.printStackTrace();
        }

        if (userId == null) {
            throw new TokenInvalidException();
        }
        return userId;
    }

    @Override
    public Boolean isAvailable(String token) {

        try {
            String userId = this.getUserId(token);
            String key = RedisKey.ACCOUNT_TOKEN;
            if (userId != null) {
                key = BuilderKey.build(key, userId);
                String orgToken = redisService.get(key);
                if (orgToken != null && orgToken.equals(token)) {
                    return true;
                }
            }
        } catch (TokenInvalidException e) {
            e.printStackTrace();
        }

        return false;
    }

    @Override
    public CommonResult<AppAccountInfo> getAvailAccount(String token, String version, String platform) {
        AppAccountInfo appAccountInfo = this.getAccountByToken(token);
        if (null == appAccountInfo) {
            return CommonResult.tokenEnable("token失效");
        }
        if (appAccountInfo.getType() == AccountType.USER) {
            return CommonResult.create(null, CommonResult.NO_PERMISSION, "没有权限访问数据");
        }
        //验证角色权限, 可以根据是否更新了账户角色菜单关系的ID
        if (StringUtils.isNotEmpty(version)) {
            boolean flag = appMenuService.checkUpdate(Long.valueOf(appAccountInfo.getId()));
            if (flag) {
                return CommonResult.create(null, CommonResult.UPDATE_PERMISSION, "权限更新了,请重新登录");
            }
        }
        return CommonResult.success(appAccountInfo);
    }

    @Override
    public AppAccountInfo getAccountByToken(String token) {
        //验证token是否有效, 无效返回null
        if (!this.isAvailable(token)) {
            return null;
        }
        AppAccountInfo accountInfo = null;
        try {
            String userId = this.getUserId(token);

            try {
                lock.readLock().lock();
                accountInfo = accountInfoMapper.selectByPrimaryKey(Integer.valueOf(userId));
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.readLock().unlock();
            }
            return accountInfo;
        } catch (TokenInvalidException e) {
            e.printStackTrace();
            return null;
        }
    }
}
